/* I2C interface implementation */

/* Copyright (C) 2009 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <stdio.h>
#include <avr/io.h>
#include "global.h"
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <util/twi.h>
#include "i2c.h"
#include "serial.h"

void i2c_init(void) {
 /* set bitrate to 100kHz on a 20MHz CPU */
 DDRC &= ~((1 << PORTC0) | (1 << PORTC1)); /* make sure they're inputs */
 PORTC |= ((1 << PORTC0) | (1 << PORTC1)); /* enable pull-ups */
 TWSR = 0;
 TWBR = (F_CPU / 100000UL - 16) / 2;
 TWCR = (1 << TWEN); 
 return;
}

uint8_t i2c_read(uint8_t devaddr, uint8_t addr, uint8_t *bytes, int8_t len) {
 uint8_t n = 0, twst, attempt = 0;

 restart: /* where we come into if we failed somewhere along the line */
 if (attempt++ >= 200) { /* taking too long */
  serial0_tx_PGM(PSTR("i2c: device not responding at 0x"));
  serial0_tx_hex(devaddr);
  serial0_tx_PGM(PSTR(", giving up\r\n"));
  return 0;
 }

 begin: /* where we come into if we're just trying to get the bus */
 
 /* start condition */
 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);

 while (!(TWCR & (1 << TWINT)));

 /* check we entered start mode okay */
 switch ((twst = TW_STATUS)) {
	case TW_REP_START: /* odd but valid */
	case TW_START:
		break;
	case TW_MT_ARB_LOST:
		serial0_tx_PGM(PSTR("i2c: aribitration lost\r\n"));
		goto begin;
	default:
		serial0_tx_PGM(PSTR("i2c: BUG: TWI failure at start\r\n"));
		return 0; /* not in start condition, don't send stop */
 }

 /* write the device address */
 TWDR = (devaddr << 1); /* force write */
 TWCR = (1 << TWINT) | (1 << TWEN);

 /* wait for it to complete send */
 while (!(TWCR & (1 << TWINT)));

 switch ((twst = TW_STATUS)) {
	case TW_MT_SLA_ACK:
		break; /* device accepted selection */
	case TW_MT_SLA_NACK:
		goto restart; /* device busy */
	case TW_MT_ARB_LOST:
		goto begin; /* someone stepped on our bus */
	default:
		serial0_tx_PGM(PSTR("i2c: bug? some device select fail?\r\n"));
		goto error; /* and send stop condiition */
 }
 
 TWDR = addr; /* select the address given to us */
 TWCR = (1 << TWINT) | (1 << TWEN);

 while (!(TWCR & (1 << TWINT))); /* wait for complete */
 
 switch ((twst = TW_STATUS)) {
	case TW_MT_DATA_ACK:
		break; /* all good */
	case TW_MT_DATA_NACK:
		serial0_tx_PGM(PSTR("i2c: device didn't like address byte\r\n"));

		goto error; /* er, guess it doesn't like that? */
	case TW_MT_ARB_LOST:
		serial0_tx_PGM(PSTR("i2c: arbitration lost\r\n"));
		goto begin;
	default:
		serial0_tx_PGM(PSTR("i2c: bug? bad addr byte handling\r\n"));
		goto error;
 }

 /* Switch to master receive mode, by doing a repeated-start */
 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);

 while (!(TWCR & (1 << TWINT)));

 switch ((twst = TW_STATUS)) {
	case TW_START:		/* odd, but okay */
	case TW_REP_START:
		break;
	case TW_MT_ARB_LOST:
		serial0_tx_PGM(PSTR("i2c: arbitration lost\r\n"));
		goto begin;
	default:
		serial0_tx_PGM(PSTR("i2c: bug? bad repeated start?\r\n"));
		goto error;
 }

 /* send the request to read from the device now */
 TWDR = (devaddr << 1) | 1; /* READ */
 
 TWCR = (1 << TWINT) | (1 << TWEN); 
 
 while (!(TWCR & (1 << TWINT)));

 switch ((twst = TW_STATUS)) {
	case TW_MR_SLA_ACK:
		break;
	case TW_MR_SLA_NACK:
		serial0_tx_PGM(PSTR("i2c: device refused read\r\n"));
		goto error;
	case TW_MR_ARB_LOST:
		serial0_tx_PGM(PSTR("i2c: arbitration lost\r\n"));
		goto begin;
	default:
		serial0_tx_PGM(PSTR("i2c: bug? in read device select\r\n"));
		goto error;
 }

 /* read bytes so long as we don't exceed LEN or
    get a failed ack */

 for (n = 0; len > 0; len--) {
  if (len == 1) {
   TWCR = (1 << TWINT) | (1 << TWEN); /* NACK last byte */
  } else {
   TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
  }

  while (!(TWCR & (1 << TWINT)));

  switch ((twst = TW_STATUS)) {
	case TW_MR_DATA_NACK:
		if (len != 1) { /* ignore this is this was last byte anyway */
		 len = 0;
  		 serial0_tx_PGM(PSTR("i2c: device had no more bytes to give!\r\n"));
		}
		/* fallthru to store the byte */
	case TW_MR_DATA_ACK:
		bytes[n] = TWDR;
		n++;
		break;
	default:
		serial0_tx_PGM(PSTR("i2c: bug? in RX\r\n"));
		goto error;
  }
 }

 /* finally, we have no more bytes to receive, so send stop */
 error: /* where we land if we've given up */

 TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);

#ifdef DEBUG_I2C
 serial0_tx_PGM(PSTR("i2c: read "));
 serial0_tx_dec(n);
 serial0_tx_PGM(PSTR(" bytes from 0x"));
 serial0_tx_hex(devaddr);
 serial0_tx_cr();
#endif

 return n;
}

// switch (addr_rw & 0x1) {
// 	case 0x0: /* WRITE */
//
//		/* so we have the device selected, we can now write */
//
//		/* okay, now iterate thru the bytes we have */
//		for (n = 0; n < len; n++) {
//		 /* load the buye */
//		 TWDR = bytes[n];
//		 TWCR = (1 << TWINT) | (1 << TWEN);
//
//		 while (!(TWCR & (1 << TWINT)));
//	
//		 switch ((twst = TW_STATUS)) {
//			case TW_MT_DATA_NACK:
//				serial0_tx_PGM(PSTR("i2c: failed write\r\n"));
//				goto error; /* write protect? */
//			case TW_MT_DATA_ACK:
//				break; /* it's good */
//			default:
//				serial0_tx_PGM(PSTR("i2c: bug?\r\n"));
//				goto error; /* and send stop */
//		 }	
//		}
//		break;
//



